#pragma once

#define SELAH_COMPILER_MSVC 1
#define SELAH_COMPILER_CLANG 2
#define SELAH_COMPILER_GCC 3

/**
 * Platforms.
 */
#define SELAH_PLATFORM_WINDOWS 1
#define SELAH_PLATFORM_LINUX 2

/**
 * Determine the target platform in use.
 * http://sourceforge.net/p/predef/wiki/OperatingSystems/
 */
#ifndef SELAH_PLATFORM
#if defined(_WIN64)
#define SELAH_PLATFORM SELAH_PLATFORM_WINDOWS
#elif defined(__linux__)
#define SELAH_PLATFORM SELAH_PLATFORM_LINUX
#else
#error "Unsupported target platform"
#endif
#endif // SELAH_PLATFORM

#define SELAH_WINDOWS (SELAH_PLATFORM == SELAH_PLATFORM_WINDOWS)
#define SELAH_LINUX (SELAH_PLATFORM == SELAH_PLATFORM_LINUX)


/**
 * Shared library (DLL) export and import.
 */

#if SELAH_WINDOWS
#define SELAH_API_EXPORT __declspec(dllexport)
#define SELAH_API_IMPORT __declspec(dllimport)
#elif SELAH_LINUX
#define SELAH_API_EXPORT __attribute__((visibility("default")))
#define SELAH_API_IMPORT
#endif

#ifdef SELAH_DLL
#define SELAH_API SELAH_API_EXPORT
#else // SELAH_DLL
#define SELAH_API SELAH_API_IMPORT
#endif // SELAH_DLL

/**
 * Force inline.
 */
#if SELAH_MSVC
#define SELAH_FORCEINLINE __forceinline
#elif SELAH_CLANG | SELAH_GCC
#define SELAH_FORCEINLINE __attribute__((always_inline))
#endif

#define TIMER_BEGIN() \
    { \
    std::chrono::steady_clock::time_point start = std::chrono::high_resolution_clock::now();

#define TIMER_END(_str) \
        std::chrono::steady_clock::time_point end = std::chrono::high_resolution_clock::now();\
        float delta_t = std::chrono::duration<float, std::milli>(end - start).count(); \
        printf("%s time : %.2f ms\n", _str, delta_t);\
    }

/**
 * Implement logical operators on a class enum for making it usable as a flags enum.
 */
// clang-format off
#define SELAH_ENUM_CLASS_OPERATORS(e_) \
    inline e_ operator& (e_ a, e_ b) { return static_cast<e_>(static_cast<int>(a)& static_cast<int>(b)); } \
    inline e_ operator| (e_ a, e_ b) { return static_cast<e_>(static_cast<int>(a)| static_cast<int>(b)); } \
    inline e_& operator|= (e_& a, e_ b) { a = a | b; return a; }; \
    inline e_& operator&= (e_& a, e_ b) { a = a & b; return a; }; \
    inline e_  operator~ (e_ a) { return static_cast<e_>(~static_cast<int>(a)); } \
    inline bool is_set(e_ val, e_ flag) { return (val & flag) != static_cast<e_>(0); } \
    inline void flip_bit(e_& val, e_ flag) { val = is_set(val, flag) ? (val & (~flag)) : (val | flag); }
// clang-format on